﻿//Copyright (c) 2010 Christopher E. S. King (http://chriskingconsulting.com)
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 
// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do 
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Collections;

using King.Extensions;
using King.Text;
using King.Declarative;
using System.Reflection;

namespace King.Automation {

    internal sealed class CommandHelpHelper : TextDeclaration {

        internal const int SummaryLineLength = 70;
        internal const int ParameterLineLength = 50;
        internal const int MinWidth = 10;

        internal static IEnumerable Header(CommandAssemblyInfo commandAssembly) {
            yield return new TextSingleSpaced() {
                new TextJoin(" ") {
                    commandAssembly.Name, "-", 
                    commandAssembly.Description + ",", 
                    "Version", commandAssembly.Version,
                }, 
                
                commandAssembly.Copyright,
            };
        }
        internal static IEnumerable ParameterDocumentation(CommandParameterInfo parameter) {
            yield return parameter.Documentation;

            if (parameter.IsExistingFile)
                yield return "File must already exist.";
            if (parameter.IsExistingDirectory)
                yield return "Directory must already exist.";
            if (parameter.IsExistingPath)
                yield return "Directory or file must already exist.";

            if (parameter.Type.IsEnum)
                yield return new TextJoin() { "Valid values are ", new TextJoin(", ", lastDelimiter: ", or ") { 
                    Enum.GetNames(parameter.ElementType)
                }, "." };

            if (parameter.IsEnumeration)
                yield return "Use ';' to separate multipule values.";

            if (parameter.DefaultValue != null) {
                var defaultValue = parameter.DefaultValue;
                if (parameter.IsString)
                    defaultValue = new TextJoin() { "\"", defaultValue, "\"" };
                yield return new TextJoin() { "Default value is ", defaultValue, "." };
            }
        }
        internal static IEnumerable ParametersDocumentation(CommandInfo commandInfo) {
            var parameters = commandInfo.Parameters();
            var maxWidth = new[] { MinWidth, parameters.MaxOr(o => o.Name.Length, 0) }.Max();

            return new TextSingleSpaced() {
                "Parameters:",

                new TextDoubleSpaced() {
                    from o in parameters
                    let name = (o.IsSwitch ? "/" : null) + o.Name.ToLowerFirst()
                    select new CommandSummary(
                        lineLength: SummaryLineLength,
                        indent: maxWidth + 3,
                        header: name) {

                        new TextJoin() { ParameterDocumentation(o) }
                    }
                }
            };
        }
        internal static IEnumerable GroupHeader(CommandGroupInfo commandGroup) {
            yield return new TextWrap(SummaryLineLength) { 
                new TextJoin() { commandGroup.Documentation } 
            };
        }
        internal static IEnumerable CommandDocumentation(CommandInfo commandInfo) {
            var header = commandInfo.Assembly.Name + " " + commandInfo.Name;

            yield return new CommandSummary(
                lineLength: ParameterLineLength,
                indent: header.Length,
                header: header) {

                new TextJoin(" ") {

                    // required option
                    commandInfo.RequiredSwitch == null ? null :
                        "/" + commandInfo.RequiredSwitch.ToLowerFirst(),

                    // options and targets
                    from o in commandInfo.Parameters()
                    let name = o.DocumentationName
                    let namePrefix = o.IsSwitch ? "/" : null
                    let prefix = o.IsOptional ? "[" : null
                    let suffix = o.IsOptional ? "]" : null
                    select new TextJoin(":", prefix: prefix, suffix: suffix) {
                        new TextJoin(prefix: namePrefix) { name },
                        o.DocumentationValue
                    }
                }
            };
        }
    }

    [CommandGroup]
    internal sealed class AssemblyHelpCommand : Command {

        [Command]
        public AssemblyHelpCommand(string assemblyName)
            : base(assemblyName) {
        }

        private IEnumerable Execute(DeclarationContext context, string assemblyName) {
            var assembly = CommandAssemblyInfo.GetAssembly(Assembly.Load(assemblyName));
            var nl = Environment.NewLine; 

            yield return CommandHelpHelper.Header(assembly);
            yield return nl;

            yield return nl;
            yield return "Type " + assembly.Name + " <command name> /? for command line description." + nl;
            yield return "Type " + assembly.Name + " <command name> /?? for command line and parameter description." + nl;

            if (assembly.Groups().None())
                yield break;

            yield return nl;

            var width = new[] { 
                assembly.Groups().Max(o => o.Name.Length), 
                CommandHelpHelper.MinWidth 
            }.Max();

            yield return new TextDoubleSpaced(prefix: "Commands:" + nl) {
                from o in assembly.Groups()
                select new CommandSummary(
                    header: new ConsoleMarkup(foreground: ConsoleColor.White) { 
                        o.Name.ToLowerFirst().PadRight(width)
                    }, 
                    indent: width, 
                    lineLength: CommandHelpHelper.SummaryLineLength) {

                    o.Documentation
                }
            };
        }
    }
    [CommandGroup]
    internal sealed class GroupHelpCommand : Command {

        [Command]
        internal GroupHelpCommand(string groupAqn)
            : base(groupAqn) {
        }

        private IEnumerable Execute(DeclarationContext context, string groupAqn) {
            var @group = CommandGroupInfo.GetGroup(Type.GetType(groupAqn));

            return new TextDoubleSpaced() {
                CommandHelpHelper.Header(group.Assembly),
                CommandHelpHelper.GroupHeader(group),

                from o in @group.Infos()
                select new object[] {
                    CommandHelpHelper.CommandDocumentation(o),
                    //CommandHelpHelper.ParametersDocumentation(o)
                }
            };
        }
    }
    internal sealed class CommandHelpCommand : Command {

        public const string DetailedHelpOption = "/??";

        private CommandInfo m_command;

        internal CommandHelpCommand(CommandInfo command) {
            m_command = command;
        }

        private IEnumerable Execute() {
            return new TextDoubleSpaced() {
                CommandHelpHelper.Header(m_command.Assembly),
                CommandHelpHelper.GroupHeader(m_command.Group),
                CommandHelpHelper.CommandDocumentation(m_command),
                CommandHelpHelper.ParametersDocumentation(m_command)
            };
        }
    }

    internal sealed class CommandSummary : TextDeclaration {

        private object m_header;
        private int m_lineLength;
        private int m_indent;

        public CommandSummary(object header, int lineLength, int indent) {
            m_header = header;
            m_indent = indent;
            m_lineLength = lineLength;
        }

        protected override IEnumerable Children(DeclarationContext context) {
            return new TextJoin {
                m_header, "  ", new TextIndent(m_indent + 2) {
                    new TextWrap(m_lineLength) { Children() }
                }
            };
        }
    }
}